package com.nf;

import javax.sql.DataSource;
import java.sql.*;
import java.util.Arrays;

//abstract：抽象
//父类不能直接用，因为功能不完整，所以父类是抽象的
public abstract class  MiniAbstractQueryRunner {
    private volatile boolean pmdKnownBroken=false;

    //已经不推荐这种写法，应该改为private修饰，之所以没有改，为了兼容
    //不推荐的原因是因为提供了一个getDataSource的方法，此类的子类都可以通过调用getDataSource方法的形式来获取这个变量
    @Deprecated
    protected final DataSource ds;

    //每次写构造函数，就先写一个有所有参数的构造函数，比如此类最后一个构造函数
    //其他的构造函数都是调用
    public MiniAbstractQueryRunner(DataSource ds) {
        this.ds = ds;
    }

    public MiniAbstractQueryRunner() {
        ds=null;
    }

    public MiniAbstractQueryRunner(boolean pmdKnownBroken) {
        this.pmdKnownBroken = pmdKnownBroken;
        ds=null;
    }

    public MiniAbstractQueryRunner(boolean pmdKnownBroken, DataSource ds) {
        this.pmdKnownBroken = pmdKnownBroken;
        this.ds = ds;
    }

    protected void close(Statement stmt) throws SQLException {
        MiniDbutils.close(stmt);
    }

    protected void close(Connection connection) throws SQLException {
        MiniDbutils.close(connection);
    }

    protected void close(ResultSet rs) throws SQLException {
        MiniDbutils.close(rs);
    }

    public DataSource getDataSource(){
        return this.ds;
    }

    //DataSource通常会采用连接池的方式，比如Alibaba的Druid
    protected Connection prepareConnection() throws SQLException{
        if (this.getDataSource() == null) {
            throw new SQLException("错误");
        }
        //抛出去的异常是getDataSource中的getConnection的异常
        return this.getDataSource().getConnection();
    }

    protected void rethrow(SQLException cause, String sql, Object... params) throws SQLException {
        String causeMessage= cause.getMessage();
        if (causeMessage == null) {
            causeMessage="";
        }
        StringBuffer msg=new StringBuffer(causeMessage);
        msg.append("Query");
        msg.append(sql);
        msg.append("Parameters");

        if (params == null) {
            msg.append("[]");
        }else{
            msg.append(Arrays.deepToString(params));
        }
        SQLException exception=new SQLException(msg.toString(),cause.getSQLState());
        exception.setNextException(cause);
        throw exception;
    }


    /**
     * 为什么要单独弄一个一行的代码方法呢，原因：
     * 1.利用此方法可以完善对象的创建，比如可能会集中对statement进行一些设置，比如超时设置，字段数量
     * 2.因为是方法，而且protector修饰符，所以就给了子类以重写的机会来改变父类创建此对象的方式
     * @param conn
     * @param sql
     * @return
     * @throws SQLException
     */
    protected PreparedStatement preparedStatement(Connection conn, String sql) throws SQLException {
        PreparedStatement ps=conn.prepareStatement(sql);
        return ps;
    }

    public void fillStatement(PreparedStatement stmt, Object... params) throws SQLException {
        ParameterMetaData pmd=null;
        //！相当于==false
        if (!pmdKnownBroken) {
            try {
                //参数元数据简单的认为是描述sql中？
                pmd=stmt.getParameterMetaData();
                if (pmd == null) {
                    pmdKnownBroken=true;
                }else {
                    //获取的就是sql语句中?的个数
                    int stmtCount=pmd.getParameterCount();
                    //值得数量的处理，没有值就为0
                    int paramsCount=params==null?0: params.length;
                    //问号的数量与值得数量不匹配就报错
                    if (stmtCount != paramsCount) {
                     throw new SQLException("Wrong number of parameters:expected" +stmtCount+ ", was given "+paramsCount);
                    }
                }
            } catch (SQLFeatureNotSupportedException e) {
                pmdKnownBroken=true;
            }
        }
        if (params == null) {
            return;
        }
        for (int i = 0; i < params.length; i++) {
            if (params[i] != null) {
                stmt.setObject(i+1,params[i]);
            }else {
                //设置列的空值，是调用setNull(列号，列的类型)这种语法
                //当无法知道列的类型时，就指定varchar类型，因为很多jdbc驱动是允许这样做
                //如果驱动实现了ParameterMetaData的getParameterType方法，那么就用此方法来精确到获取列的类型
                int sqlType=Types.VARCHAR;
                if (!pmdKnownBroken) {
                    try {
                        sqlType=pmd.getParameterType(i+1);
                    } catch (SQLException e) {
                        pmdKnownBroken=true;
                    }
                }
                stmt.setNull(i+1,sqlType);
            }
        }
    }
}
